package com.zmn.oms.third.core.tracker;

import com.zmn.common.constant.GlobalConsts;
import com.zmn.oms.common.constant.FacadeTrackConsts;
import com.zmn.oms.common.enums.ChannelFacade;
import com.zmn.oms.common.utils.ExceptionUtil;
import com.zmn.oms.manager.utils.SpringBeanFactoryUtil;
import com.zmn.oms.model.entity.log.FacadeTrackLog;
import com.zmn.oms.zmn.business.interfaces.log.FacadeTrackLogBService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;

/**
 * 类描述：渠监控道跟踪器
 *
 *  使用示例：
 *      1. 开启跟踪
 *      FacadeTracker.getTracker()
 *          .dataIn(ChannelFacade.DAOWEI);
 *
 *      2. 记录处理进度说明，异常等
 *      FacadeTracker.getTracker()
 *          .orderId(xxx)
 *          .reqSummary(xxx);
 *          .remark(xxx)
 *          .error(e)
 *          .remark(xxx)
 *          .error(e)
 *          .respSummary(xxx);
 *      3. 结束跟踪并保存跟踪记录
 *      FacadeTracker.getTracker()
 *          .success(true)
 *          .commit();
 *
 * @author lhl
 * @date 2020/12/07 14:46
 */
public class FacadeTracker {

    /**
     * 获取跟踪器实例
     * @return
     */
    public static FacadeTrackerCore getTracker() {
        return FacadeTrackerCore.getInstance();
    }

    /**
     * 丢弃当前跟踪器
     */
    public static void discardTracker() {
        FacadeTrackerCore.discard();
    }

    /**
     * 类描述：渠道对接跟踪器
     *
     * @author lhl
     * @date 2019/11/28 11:34
     *
     * 方法介绍：
     *  使用方法分为三个部分：1.开启跟踪 2.记录跟踪数据 3.提交跟踪数据并结束跟踪
     *      1.开启跟踪，开启跟踪方法为必须调用方法
     *      FacadeTrackerCore.getInstance().dataIn(ChannelFacade.DAOWEI);  // 开启一个由第三方渠道同步到啄木鸟的跟踪
     *      FacadeTrackerCore.getInstance().dataOut(ChannelFacade.DAOWEI); // 开启一个由啄木鸟同步到第三方渠道的跟踪
     *
     *      2.记录跟踪数据，均为非必须调用方法
     *      FacadeTrackerCore.getInstance().orderId(orderId);       // 关联的订单ID，非必须，同步新单失败除外（未获取到新单ID）
     *      FacadeTrackerCore.getInstance().reqSummary(str);        // 请求数据摘要，非必须，多次调用以最后一次为准
     *      FacadeTrackerCore.getInstance().respSummary(str);       // 响应数据摘要，非必须，多次调用以最后一次为准
     *      FacadeTrackerCore.getInstance().remark(str);            // 追加备注，用于业务人员查看订单跟踪情况，非必须，多次调用结果累加
     *      FacadeTrackerCore.getInstance().error(str [,exception]);// 记录异常，非必须，多次调用结果累加
     *      FacadeTrackerCore.getInstance().success(true [,str]);   // 标识同步是否成功，非必须
     *
     *      3.提交跟踪数据并结束跟踪
     *      FacadeTrackerCore.getInstance().commit();       // 必须，否则会丢失本次跟踪数据
     *      FacadeTrackerCore.discard();                    // 手动丢弃本次跟踪数据
     *
     * 使用说明：
     *      1. 以上方法都支持链式编程；
     *      2. 外部使用时推荐使用 FacadeTracker.tracker().xxx() 方式，不建议直接使用 FacadeTrackerCore.getInstance().xxx() 方式调用；
     */
    @Slf4j
    public static class FacadeTrackerCore {

        /**
         * trackHolder
         */
        private static ThreadLocal<FacadeTrackerCore> trackHolder = new ThreadLocal();
        /**
         * 特定格式的时间格式化工具
         */
        private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmssSSS");
        static {
            dateFormat.setTimeZone(TimeZone.getTimeZone("GMT+8:00"));
        }
        /**
         * 跟踪事件对象
         */
        private FacadeTrackLog facadeTrackLog;
        /**
         * 备注缓存，用于缓存多次添加的备注
         */
        private StringBuilder remarkBuffer;
        /**
         * 导致同步程序中断的异常
         */
        private List<Exception> exceptions;


        /* ======================= 开启跟踪 ====================== */
        /**
         * 开启追踪，标识本次追踪的数据流向：第三方渠道-->系统
         * @param channelFacade 对接渠道的接口标识
         * @return
         */
        public FacadeTrackerCore dataIn(ChannelFacade channelFacade) {
            return open(channelFacade, FacadeTrackConsts.DATA_IN);
        }

        /**
         * 开启追踪，标识本次追踪的数据流向：系统-->第三方渠道
         * @param channelFacade 对接渠道的接口标识
         * @return
         */
        public FacadeTrackerCore dataOut(ChannelFacade channelFacade) {
            return open(channelFacade, FacadeTrackConsts.DATA_OUT);
        }

        /* ======================= 跟踪中 ====================== */
        /**
         * 添加订单ID
         * @param orderIdStr
         */
        public FacadeTrackerCore orderId(String orderIdStr) {
            Long orderId = null;
            try {
                orderId = Long.parseLong(orderIdStr);
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
            FacadeTrackLog log = getLog();
            if (orderId != null && log != null) {
                log.setOrderId(orderId);
            }
            return this;
        }
        /**
         * 添加订单ID
         * @param orderId
         */
        public FacadeTrackerCore orderId(Long orderId) {
            FacadeTrackLog log = getLog();
            if (log != null) {
                log.setOrderId(orderId);
            }
            return this;
        }

        /**
         * 设置请求数据摘要
         */
        public FacadeTrackerCore reqSummary(String reqSummary) {
            FacadeTrackLog log = getLog();
            if (log != null) {
                log.setReqSummary(reqSummary);
            }
            return this;
        }

        /**
         * 设置响应数据摘要
         */
        public FacadeTrackerCore respSummary(String respSummary) {
            FacadeTrackLog log = getLog();
            if (log != null) {
                log.setRespSummary(respSummary);
            }
            return this;
        }

        /**
         * 追加备注，建议每次追加的备注内容都以分号“；”结尾
         * @param remark
         */
        public FacadeTrackerCore remark(String remark) {
            if (this.remarkBuffer != null) {
                this.remarkBuffer.append(remark).append("\n");
            }
            return this;
        }

        /**
         * 追加备注，建议每次追加的备注内容都以分号“；”结尾
         * @param template
         * @param remark
         */
        public FacadeTrackerCore remark(String template, String... remark) {
            return remark(String.format(template, remark));
        }

        /**
         * 记录异常信息，本方法在一次跟踪记录中可以调用多次，不会直接影响本次跟踪的订单的同步结果。
         * 本方法可以传入被吃掉处理的异常，也可以传入上抛的异常。
         * @param e
         * @param errorMsg
         */
        public FacadeTrackerCore error(Exception e, String errorMsg) {
            if (this.exceptions == null) {
                this.exceptions.add(e);
            }

            if (this.remarkBuffer != null && StringUtils.isNotBlank(errorMsg)) {
                this.remarkBuffer.append(errorMsg).append("\n");
            }
            return this;
        }

        /**
         * 记录异常信息，本方法在一次跟踪记录中可以调用多次，不会直接影响本次跟踪的订单的同步结果。
         * 本方法可以传入被吃掉处理的异常，也可以传入上抛的异常。
         * @param e
         * @param template
         * @param strings
         */
        public FacadeTrackerCore error(Exception e, String template, String... strings) {
            return error(e, String.format(template, strings));
        }

        /**
         * 记录异常信息，本方法在一次跟踪记录中可以调用多次，不会直接影响本次跟踪的订单的同步结果。
         * 本方法可以传入被吃掉处理的异常，也可以传入上抛的异常。
         * @param errorMsg
         * @param e
         */
        @Deprecated
        public FacadeTrackerCore error(String errorMsg, Exception e) {
            if (this.exceptions == null) {
                this.exceptions.add(e);
            }

            if (this.remarkBuffer != null && StringUtils.isNotBlank(errorMsg)) {
                this.remarkBuffer.append(errorMsg).append("\n");
            }
            return this;
        }

        /* ======================= 完成跟踪 ====================== */
        /**
         * 标记本次跟踪的订单同步结果
         */
        public FacadeTrackerCore success(boolean isSuccess) {
            return success(isSuccess, null);
        }
        /**
         * 标记本次跟踪的订单同步结果
         * @param isSuccess
         * @param errorMsg
         */
        public FacadeTrackerCore success(boolean isSuccess, String errorMsg) {
            FacadeTrackLog log = getLog();
            if (log == null) {
                return this;
            }

            if (StringUtils.isBlank(errorMsg)) {
                errorMsg = isSuccess ? "成功" : "失败";
            }
            log.setSuccess(isSuccess ? GlobalConsts.YES : GlobalConsts.NO);
            log.setErrorMsg(errorMsg);
            return this;
        }

        /**
         * 确定订单同步结果，提交当前跟踪事件，关闭本次跟踪过程。
         * 确当订单同步结果的逻辑如下：
         *  1.如果本方法调用前，调用过success()或fail()方法，则以这两个方法标识的结果作为最终同步结果，是否调用error()方法不对结果产生影响；
         *  2.如果本方法调用前，未调用过success()和fail()方法，但调用过error()方法，则设置同步结果为失败；
         *  3.如果本方法调用前，success()、fail()和error()方法均未调用过，则设置同步结果为成功；
         */
        public void commit() {
            try {
                FacadeTrackerCore track = trackHolder.get();
                if (track == null) {
                    return;
                }
                FacadeTrackLog dockingLog = track.facadeTrackLog;
                if (dockingLog == null) {
                    return;
                }

                {// 完善日志信息
                    // 结束时间
                    dockingLog.setEndTime(currentTime());
                    // 备注
                    StringBuilder remarkBuffer = track.remarkBuffer;
                    if (remarkBuffer.length() > 0) {
                        dockingLog.setRemark(remarkBuffer.toString());
                    }
                    // 是否存在异常
                    List<Exception> exceptions = track.exceptions;
                    boolean isError = !exceptions.isEmpty();
                    dockingLog.setError(isError ? GlobalConsts.YES : GlobalConsts.NO);
                    // 如果同步过程中出现了异常，保存这些异常信息
                    if (isError) {
                        StringBuilder errorDetailBuilder = new StringBuilder();
                        // 收集异常堆栈信息，越后添加的越重要，要优先显示
                        for (int i=exceptions.size()-1; i<0; i--) {
                            errorDetailBuilder.append(ExceptionUtil.getStackTrace(exceptions.get(i)));
                        }
                        dockingLog.setErrorStackTrace(errorDetailBuilder.toString());
                    }
                    // 如果没有显示的标记同步结果，即没有调用success()和fail()方法，则发生异常判为失败，否则判为成功
                    if (dockingLog.getSuccess() == null) {
                        dockingLog.setSuccess(isError ? GlobalConsts.NO : GlobalConsts.YES);
                    }
                }

                // 保存追踪信息
                FacadeTrackLogBService logBService = SpringBeanFactoryUtil.getBean("facadeTrackLogBService");
                if (logBService != null) {
                    logBService.save(dockingLog);
                }
                else {
                    log.warn("[ChannelDockingTracker] No FacadeTrackLogBService instance found!");
                }

                // 清空当前持有的跟踪事件
                trackHolder.remove();
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        }

        /**
         * 获取追踪示例
         * @return
         */
        public static FacadeTrackerCore getInstance() {
            FacadeTrackerCore tracker = trackHolder.get();
            if (tracker == null) {
                tracker = new FacadeTrackerCore();
                trackHolder.set(tracker);
            }
            return tracker;
        }

        /**
         * 丢弃追踪示例
         * @return
         */
        public static void discard() {
            trackHolder.remove();
        }

        /* *******************************************/
        /* ************* Private Method **************/
        /* *******************************************/

        /**
         * 开启跟踪
         * @param type 数据走向类型
         * 使用常量：FacadeTrackConsts.DATA_IN, FacadeTrackConsts.DATA_OUT
         * @return
         */
        private FacadeTrackerCore open(ChannelFacade channelFacade, Integer type) {
            FacadeTrackLog facadeTrackLog = new FacadeTrackLog();
            facadeTrackLog.setFacadeId(channelFacade.getId());
            facadeTrackLog.setType(type);
            facadeTrackLog.setBeginTime(currentTime());
            facadeTrackLog.setOrderId(0L);    // 默认订单号为0，多用于添加新单失败时，没有订单号的情况
            this.facadeTrackLog = facadeTrackLog;

            this.remarkBuffer = new StringBuilder();
            this.exceptions = new ArrayList<>();

            return this;
        }

        /**
         * 获取当前跟踪的事件对象
         * @return
         */
        private FacadeTrackLog getLog() {
            return this.facadeTrackLog;
        }

        /**
         * 获取当前时间的指定格式化形式
         * @return
         */
        private static long currentTime() {
            return Long.parseLong(dateFormat.format(new Date()));
        }
    }

}
